/*
 * brokerMain.cpp
 *
 *  Created on: 21/07/2014
 *      Author: nicolas
 */

#include "../defines.h"
#include "../ipc/ColaMensajes.h"
#include <list>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/unistd.h>
#include <syslog.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
#include <string>
#include "../Anillo/Anillo.h"
#include "../comun/ServidorRemoto.h"
#include <fstream>
#include <sstream>
#include "../comun/TokenCompartido.h"

using namespace std;

#define RUTA_CONFIG "./Puertos.config"

#define TIEMPO_ESPERA_TOKEN 10

int lanzarConectorClientes(int puerto) {
	stringstream ss;
	ss << puerto;
	int pid = fork();
	if (pid == 0) {
		execlp("./brokerConector", "BrokerConector", ss.str().c_str(), NULL);
		perror("Error en el exec conector");
		exit(1);
	}
	return pid;
}

void obtenerDatosSem(int id, TipoSem& tipo, int& numero) {

	if (id == ID_MUTEX_PARQUE) {
		numero = INV_ID_MUTEX_PARQUE;
		tipo = SEM_PARQUE;
	} else if (id >= ID_MUTEX_SALA_ENT(1) && id < ID_MUTEX_SALA_ZOO(1)) {
		numero = INV_ID_MUTEX_SALA_ENT(id);
		tipo = SEM_SALA_ENTRADA;
	} else if (id >= ID_MUTEX_SALA_ZOO(1) && id < ID_SEM_SALA_ENT_LLENO(1)) {
		numero = INV_ID_MUTEX_SALA_ZOO(id);
		tipo = SEM_SALA_ZOO;
	} else if (id >= ID_SEM_SALA_ENT_LLENO(1) && id < ID_SEM_SALA_ZOO_LLENO(1)) {
		numero = INV_ID_SEM_SALA_ENT_LLENO(id);
		tipo = SEM_LLENO_ENTRADA;
	} else if (id >= ID_SEM_SALA_ZOO_LLENO(1) && id < ID_SEM_BUS(1)) {
		numero = INV_ID_SEM_SALA_ZOO_LLENO(id);
		tipo = SEM_LLENO_ZOO;
	} else if (id >= ID_SEM_BUS(1) && id < 257) {
		numero = INV_ID_SEM_BUS(id);
		tipo = SEM_BUS;
	} else {
		numero = -1;
	}
}

bool* getSem(TOKEN& token, TipoSem& tipo, int numero) {
	switch (tipo) {
	case SEM_BUS:
		return &(token.semBus[numero - 1]);
		break;
	case SEM_LLENO_ENTRADA:
		return &(token.semSalasLlenoEntrada[numero - 1]);
		break;
	case SEM_LLENO_ZOO:
		return &(token.semSalasLlenoZoo[numero - 1]);
		break;
	case SEM_SALA_ENTRADA:
		return &(token.semSalasEntrada[numero - 1]);
		break;
	case SEM_SALA_ZOO:
		return &(token.semSalasZoo[numero - 1]);
		break;
	case SEM_PARQUE:
		return &(token.semParque);
		break;
	default:
		break;
	}
	return 0;
}

bool operar(MENSAJE_BROKER& msg, TOKEN& token, int& respuesta) {
	bool resultado = true;
	respuesta = 0;
	TipoSem tipo;
	int numero;
	bool* sem;
	switch (msg.tipo) {
	case TIPO_SEM_SIGNAL:
		obtenerDatosSem(msg.idSem, tipo, numero);
		sem = getSem(token, tipo, numero);
		if (!(*sem)) {
			*sem = true;
		} else {
			resultado = false;
		}
		break;
	case TIPO_SEM_WAIT:
		obtenerDatosSem(msg.idSem, tipo, numero);
		sem = getSem(token, tipo, numero);
		if ((*sem)) {
			*sem = false;
		} else {
			resultado = false;
		}
		break;
	case TIPO_SALA_LEER_CANTPERSONAS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			respuesta = token.salasEntrada[msg.numSala - 1].cantPersonas;
		} else {
			respuesta = token.salasZoo[msg.numSala - 1].cantPersonas;
		}
		break;
	case TIPO_SALA_LEER_RESERVAS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			respuesta = token.salasEntrada[msg.numSala - 1].reservas;
		} else {
			respuesta = token.salasZoo[msg.numSala - 1].reservas;
		}
		break;
	case TIPO_SALA_LEER_PERSONAS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			respuesta =
					token.salasEntrada[msg.numSala - 1].personas[msg.numPersona];
		} else {
			respuesta =
					token.salasZoo[msg.numSala - 1].personas[msg.numPersona];
		}
		break;
	case TIPO_SALA_LEER_ESTADOBUS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			respuesta = token.salasEntrada[msg.numSala - 1].estadoBUS;
		} else {
			respuesta = token.salasZoo[msg.numSala - 1].estadoBUS;
		}
		break;
	case TIPO_SALA_ESCRIBIR_CANTPERSONAS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			token.salasEntrada[msg.numSala - 1].cantPersonas =
					msg.valorAEscribir;
		} else {
			token.salasZoo[msg.numSala - 1].cantPersonas = msg.valorAEscribir;
		}
		break;
	case TIPO_SALA_ESCRIBIR_RESERVAS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			token.salasEntrada[msg.numSala - 1].reservas = msg.valorAEscribir;
		} else {
			token.salasZoo[msg.numSala - 1].reservas = msg.valorAEscribir;
		}
		break;
	case TIPO_SALA_ESCRIBIR_PERSONAS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			token.salasEntrada[msg.numSala - 1].personas[msg.numPersona] =
					msg.valorAEscribir;
		} else {
			token.salasZoo[msg.numSala - 1].personas[msg.numPersona] =
					msg.valorAEscribir;
		}
		break;
	case TIPO_SALA_ESCRIBIR_ESTADOBUS:
		if (msg.lugarSala == Tipos::LUGAR_ENTRADA) {
			token.salasEntrada[msg.numSala - 1].estadoBUS =
					(Tipos::EstadoBUS) msg.valorAEscribir;
		} else {
			token.salasZoo[msg.numSala - 1].estadoBUS =
					(Tipos::EstadoBUS) msg.valorAEscribir;
		}
		break;
	case TIPO_PARQUE_LEER_CANTPERSONAS:
		respuesta = token.parque.cantPersonas;
		break;
	case TIPO_PARQUE_LEER_ULTIMOTICKET:
		respuesta = token.parque.ultimoTicket;
		break;
	case TIPO_PARQUE_LEER_BUSESDEREPUESTO:
		respuesta = token.parque.busesDeRepuestos;
		break;
	case TIPO_PARQUE_LEER_PARQUEABIERTO:
		respuesta = token.parque.parqueAbierto;
		break;
	case TIPO_PARQUE_ESCRIBIR_CANTPERSONAS:
		token.parque.cantPersonas = msg.valorAEscribir;
		break;
	case TIPO_PARQUE_ESCRIBIR_ULTIMOTICKET:
		token.parque.ultimoTicket = msg.valorAEscribir;
		break;
	case TIPO_PARQUE_ESCRIBIR_BUSESDEREPUESTO:
		token.parque.busesDeRepuestos = msg.valorAEscribir;
		break;
	case TIPO_PARQUE_ESCRIBIR_PARQUEABIERTO:
		token.parque.parqueAbierto = msg.valorAEscribir;
		break;
	default:
		perror("Tipo de mensaje desconocido");
		exit(1);
		break;
	}

	return resultado;
}

void getConfig(int& multi, int& uni, int& tcp, char* ipMulticast) {
	fstream file(RUTA_CONFIG, fstream::in);
	string line;
	string delimiter = ",";
	while (getline(file, line)) {
		string puerto = line.substr(0, line.find(delimiter));
		line.erase(0, line.find(delimiter) + delimiter.length());
		string valor = line.substr(0, line.find(delimiter));
		if (puerto == "Multicast") {
			multi = atoi(valor.c_str());
		} else if (puerto == "Unicast") {
			uni = atoi(valor.c_str());
		} else if (puerto == "TCP") {
			tcp = atoi(valor.c_str());
		} else if (puerto == "GrupoMulti") {
			strcpy(ipMulticast, valor.c_str());
		}
	}
	file.close();
}

int main(int argc, char **argv) {

	if (argc != 2) {
		perror("cantidad de args invalida");
		exit(1);
	}

	int puertoMulti, puertoUni, puertoTCP;
	char ipMulticast[16];

	getConfig(puertoMulti, puertoUni, puertoTCP, ipMulticast);

	int idLocal = atoi(argv[1]);

	ServidorRemoto servidor;
	int id = servidor.pedirIdBroker(idLocal, puertoTCP);

	char encabezado[80];
	sprintf(encabezado, "Broker %i", id);

	openlog(encabezado, LOG_PID | LOG_CONS, LOG_LOCAL0);

	syslog(LOG_INFO, "Iniciado con puertos multicast: %i, unicast: %i, TCP: %i",
			puertoMulti, puertoUni, puertoTCP);

	int pidConector = lanzarConectorClientes(puertoTCP);

	list<MENSAJE_BROKER> pendientes;
	ColaMensajes cola(RUTA, COLABROKER);
	ColaMensajes colaRespuesta(RUTA, COLARESPUESTA);
	ColaMensajes colaToken(RUTA, COLATOKEN);
	MENSAJE_BROKER msg;
	MENSAJE_TOKEN token;

	int timeout;
	servidor.datosBroker(timeout);
	int generacion = 0;
	Anillo* anillo = new Anillo(puertoMulti, puertoUni, puertoTCP, ipMulticast,
			timeout, idLocal, id, &cola, &colaRespuesta);
	anillo->generar(generacion++);
	anillo->interconectar();
	anillo->generarToken();

	char mostrar[80];
	sprintf(mostrar, "Comienzo logica de Token\n");
	write(fileno(stdout), mostrar, strlen(mostrar));

	bool salir = false;
	list<int> esperandoBroadcast;

	TokenCompartido shmemToken;

	signal(SIGPIPE, SIG_IGN );

	syslog(LOG_INFO, "Comienzo de logica de TOKEN");

	while (!salir) {
		if (!cola.recibir(&msg, TAM_MENSAJE_BROKER, id, false)) {
			perror("Error en cola, enviando kill");
			sprintf(mostrar, "Enviandi kill a %i\n", pidConector);
			write(fileno(stdout), mostrar, strlen(mostrar));
			kill(pidConector, SIGUSR1);
			exit(1);
		}
//		syslog(LOG_INFO, "Recibio mensaje de tipo %i", msg.tipo);
		if (msg.tipo == TIPO_TOKEN) {
			if (!colaToken.recibir(&token, TAM_MENSAJE_TOKEN, id, false)) {
				perror("Error en cola, enviando kill");
				sprintf(mostrar, "Enviandi kill a %i\n", pidConector);
							write(fileno(stdout), mostrar, strlen(mostrar));
				kill(pidConector, SIGUSR1);
				exit(1);
			}

			for (list<MENSAJE_BROKER>::iterator it = pendientes.begin();
					it != pendientes.end();) {
				int respuesta;
				if (operar(*it, token.token, respuesta)) {
					memcpy(&msg, &(*it), sizeof(msg));
					msg.destinatario = it->origen;
					msg.origen = id;
					msg.valorLeido = respuesta;

					if (!colaRespuesta.enviar(&msg, TAM_MENSAJE_BROKER,
							false)) {
						perror("Error en cola, enviando kill");
						sprintf(mostrar, "Enviandi kill a %i\n", pidConector);
									write(fileno(stdout), mostrar, strlen(mostrar));
						kill(pidConector, SIGUSR1);
						exit(1);
					}
					syslog(LOG_INFO,
							"Se realizo operacion pendiente de tipo %i, origen: %li, destinatario, %li",
							it->tipo, it->origen, it->destinatario);
					it = pendientes.erase(it);
				} else {
					++it;
				}
			}

			token.token.version++;
			if (token.token.version == 0)
				token.token.generacion++;

			shmemToken.actualizar(token.token);

			msg.tipo = TIPO_TOKEN;
			//usleep(1000000); //TODO sacar
			usleep(10);
			anillo->enviarASiguiente(msg);
			anillo->enviarASiguiente(token);

			anillo->lanzarTimeOutToken(TIEMPO_ESPERA_TOKEN);
			waitpid(-1, 0, WNOHANG);
		} else if (msg.tipo == TIPO_ENVIO_MSG) {
			if (msg.destinatarioBroker != id) {
				anillo->enviarASiguiente(msg);
//				msg.destinatario = msg.destinatarioBroker;
//				if (!colaRespuesta.enviar(&msg, TAM_MENSAJE_BROKER, false)) {
//					perror("Error en cola, enviando kill");
//					kill(pidConector, SIGUSR1);
//					exit(1);
//				}
			} else {
				msg.destinatario = msg.destinatarioFinal;
				if (!colaRespuesta.enviar(&msg, TAM_MENSAJE_BROKER, false)) {
					perror("Error en cola, enviando kill");
					sprintf(mostrar, "Enviandi kill a %i\n", pidConector);
								write(fileno(stdout), mostrar, strlen(mostrar));
					kill(pidConector, SIGUSR1);
					exit(1);
				}
			}
		} else if (msg.tipo == TIPO_ESPERAR_BROADCAST) {
			syslog(LOG_INFO, "Peticion de espera broadcast de %i", msg.id);
			esperandoBroadcast.push_back(msg.id);
		} else if (msg.tipo == TIPO_LANZAR_BROADCAST) {
			syslog(LOG_INFO, "Peticion de lanzar broadcast");
			if (esperandoBroadcast.size() > 0) {
				int aux = esperandoBroadcast.front();
				esperandoBroadcast.pop_front();
				msg.destinatario = aux;
				msg.destinatarioFinal = aux;
				syslog(LOG_INFO, "Broadcast dirigido a %i", aux);
				if (!colaRespuesta.enviar(&msg, TAM_MENSAJE_BROKER, false)) {
					perror("Error en cola, enviando kill");
					sprintf(mostrar, "Enviandi kill a %i\n", pidConector);
								write(fileno(stdout), mostrar, strlen(mostrar));
					kill(pidConector, SIGUSR1);
					exit(1);
				}
			} else {
				anillo->enviarASiguiente(msg);
			}
		} else if (msg.tipo == TIPO_REGENERAR_ANILLO) {
			delete anillo;
			anillo = new Anillo(puertoMulti, puertoUni, puertoTCP, ipMulticast,
					timeout, idLocal, id, &cola, &colaRespuesta);
			anillo->generar(generacion++);
			TOKEN tokAux;
			shmemToken.recuperar(tokAux);
			if (anillo->recuperarToken(tokAux.version, tokAux.generacion)) {
				sprintf(mostrar, "Reenviando Token mas actualizado\n");
				write(fileno(stdout), mostrar, strlen(mostrar));
				syslog(LOG_INFO, "Reenviando Token mas actualizado");

				msg.origen = id;
				msg.tipo = TIPO_TOKEN;
				anillo->enviarASiguiente(msg);

				token.origen = id;
				memcpy(&(token.token), &tokAux, sizeof(TOKEN));
				anillo->enviarASiguiente(token);
			}
			sprintf(mostrar, "Comienzo logica de Token\n");
			write(fileno(stdout), mostrar, strlen(mostrar));

		} else {
			syslog(LOG_INFO,
					"Agregando operacion pendiente de tipo %i, origen: %li, destinatario, %li",
					msg.tipo, msg.origen, msg.destinatario);
			pendientes.push_back(msg);
		}

	}

}

